package com.christophpurrer.shg2;

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

/**
 * ShakeEventListener is a listener that detects shake gesture.
 * 
 * Partially based on:
 * http://stackoverflow.com/questions/2317428/android-i-want-to-shake-it
 * 
 * @author Christoph Purrer
 * 
 */
public class AccelerometerManager implements SensorEventListener {

	// constants
	// minimum movement force to consider
	private static final int MINIMUM_FORCE = 20; // 10

	// minimum times in a shake gesture that the direction of movement needs to
	// change
	private static final int MINIMUM_NUMBER_OF_DIRECTION_CHANGES = 3;

	// maximum pause between movements
	private static final int MAXIMUM_PAUSE_BETHWEEN_DIRECTION_CHANGE = 200; // 200

	// maximum allowed time for shake gesture
	private static final int MAXIMUM_TOTAL_DURATION_OF_SHAKE = 400; // 400

	// properties
	// first and last time of direction changed in this cycle
	private long directionChangeFirst, directionChangeLast = 0l;

	// how many movements are considered so far
	private int directionChangeCounter = 0;

	// the last x, y and z position
	private float lastX, lastY, lastZ = 0f;

	// OnShakeListener that is called when shake is detected
	/** The accelerometer listener. */
	private AccelerometerListener accelerometerListener = null;

	// inner classes / interfaces
	/**
	 * interface for shake gesture.
	 * 
	 * @see AccelerometerEvent
	 */
	public interface AccelerometerListener {

		/**
		 * called when shake gesture is detected.
		 */
		void onShake();

		/**
		 * called when the acceleration changed.
		 * 
		 * @param x
		 * @param y
		 * @param z
		 * 
		 */
		void onAccelerationChanged(float x, float y, float z);
	}

	/**
	 * Sets the on shake listener.
	 * 
	 * @param listener
	 *            the new on shake listener
	 */
	public void setOnShakeListener(AccelerometerListener listener) {
		this.accelerometerListener = listener;
	}

	/**
	 * On sensor changed.
	 * 
	 * @param sensorEvent
	 *            the sensor event
	 */
	@Override
	public void onSensorChanged(SensorEvent sensorEvent) {
		// get sensor data
		float x = sensorEvent.values[SensorManager.DATA_X];
		float y = sensorEvent.values[SensorManager.DATA_Y];
		float z = sensorEvent.values[SensorManager.DATA_Z];

		// calculate movement
		float totalMovement = Math.abs(x + y + z - lastX - lastY - lastZ);

		if (totalMovement > MINIMUM_FORCE) {

			// get time
			long now = System.currentTimeMillis();

			// store first movement time
			if (this.directionChangeFirst == 0) {
				this.directionChangeFirst = now;
				this.directionChangeLast = now;
			}

			// check if the last movement was not long ago
			long lastChangeWasAgo = now - this.directionChangeLast;
			if (lastChangeWasAgo < MAXIMUM_PAUSE_BETHWEEN_DIRECTION_CHANGE) {

				// store movement data
				this.directionChangeLast = now;
				this.directionChangeCounter++;

				// store last sensor data
				this.lastX = x;
				this.lastY = y;
				this.lastZ = z;

				// check how many movements are so far
				if (this.directionChangeCounter >= MINIMUM_NUMBER_OF_DIRECTION_CHANGES) {

					// check total duration
					long totalDuration = now - this.directionChangeFirst;
					if (totalDuration < MAXIMUM_TOTAL_DURATION_OF_SHAKE) {
						this.resetShakeParameters();
						this.accelerometerListener.onShake();
					}
				}
			} else {
				this.resetShakeParameters();
			}
		}

		// trigger change event
		this.accelerometerListener.onAccelerationChanged(x, y, z);
	}

	/**
	 * resets the shake parameters to their default values.
	 */
	private void resetShakeParameters() {
		this.directionChangeFirst = 0;
		this.directionChangeCounter = 0;
		this.directionChangeLast = 0;
		this.lastX = 0;
		this.lastY = 0;
		this.lastZ = 0;
	}

	/**
	 * On accuracy changed.
	 * 
	 * @param sensor
	 *            the sensor
	 * @param accuracy
	 *            the accuracy
	 */
	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
	}
}